﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BFlatCompile.Nodes
{
	public class Method : ICodeContainer, IClassMember
	{
		public bool IsStatic { get; private set; }
		public string Name { get; private set; }
		public int NameIndex { get { return this.Name == null ? -1 : Variable.Table.GetId(this.Name); } }
		public MethodParam[] Params { get; private set; }
		// TODO: add return null if code does not end in return
		public Executable[] Code { get; private set; }
		public int ID { get; set; }

		private static void CheckDefaultValuesAtEnd(MethodParam[] paramList)
		{
			bool hasDefaultValue = false;
			for (int i = 0; i < paramList.Length; ++i)
			{
				MethodParam p = paramList[i];
				if (!hasDefaultValue)
				{
					if (p.Value != null)
					{
						hasDefaultValue = true;
					}
				}
				else
				{
					if (p.Value == null)
					{
						throw new Exception("All method parameters with default values must appear at the end of the param list.");
					}
				}
			}
		}

		public static List<Executable> GetParamSettingForMethodBodyHead(MethodParam[] paramList)
		{
			CheckDefaultValuesAtEnd(paramList);
			List<Executable> allCode = new List<Executable>();
			for (int i = 0; i < paramList.Length; ++i)
			{
				MethodParam p = paramList[i];
				allCode.Add(new ApplyParam(p, i, p.Token));
			}
			return allCode;
		}

		public Method(string name, bool isStatic, MethodParam[] paramList, IList<Executable> code, string filename)
		{
			this.Name = name;
			if (name != null)
			{
				Variable.Table.Register(name);
			}
			this.IsStatic = isStatic;
			this.Params = paramList;

			List<Executable> allCode = GetParamSettingForMethodBodyHead(paramList);
			allCode.AddRange(code);

			this.Code = Executable.AppendReturnIfNecessary(allCode);
			this.Filename = filename;
		}

		public string Filename { get; private set; }

		public string FullSignature
		{
			get
			{
				string output = this.owningClass.FullName + "." + this.Name + "(";
				for (int i = 0; i < this.Params.Length; ++i)
				{
					if (i > 0)
					{
						output += ", ";
					}

					output += this.Params[i].Name;
				}
				output += ")";

				return output;
			}
		}

		private Class owningClass;

		public static Method Parse(Tokens tokens)
		{
			bool isStatic = tokens.PopIfPresent("static");
			bool isAbstract = tokens.PopIfPresent("abstract");
			Token token = tokens.PopExpected("method");
			string filename = token.RelativeFile;

			string name = tokens.PopIdentifier();

			tokens.PopExpected("(");
			MethodParam[] methodParams = MethodParam.ParseList(tokens);
			tokens.PopExpected(")");

			Executable[] code;
			if (isAbstract)
			{
				if (tokens.Peek() == "{")
				{
					throw new ParserException("Abstract methods cannot have a body.", tokens.PeekToken());
				}
				tokens.PopExpected(";");
				code = new Executable[0];
			}
			else
			{
				code = CodeBlock.Parse(tokens, true);
			}
			return new Method(name, isStatic, methodParams, code, filename);
		}

		public void ResolveCode(Class c, Dictionary<object, object> parents)
		{
			this.owningClass = c;

			foreach (MethodParam mp in this.Params)
			{
				mp.ResolveCode(c, parents);
			}

			foreach (Executable exec in this.Code)
			{
				exec.ResolveCode(c, parents);
			}
		}

		public string SignaturePrefix
		{
			get { return this.IsStatic ? "M" : "m"; }
		}

		public void ResolveClassToThis(Class c)
		{
			if (this.IsStatic) // Never do this for non-static
			{
				foreach (Executable line in this.Code)
				{
					line.ResolveClassToThis(c);
				}
			}
		}
	}
}
